package robocup.component.situations;

import java.util.Arrays;

import robocup.component.DAInamiteConstants;

/**
 * This class sums up the results of the {@link PassEvaluator}.
 * 
 * The {@link PassEvaluator} tries to find out if a pass to a given point
 * succeeds without the oppponent intercepting the ball before. More than that
 * it optimizes the result trying to pass through opponents and keep the ball
 * trajectory as far away from interception as possible. By now this is only
 * used for the {@link ScoreSituation} when trying to find the best way for
 * scoring.<br>
 * <br>
 * The most important Results:
 * <ul>
 * <li>Opponents intercepting the pass from the left|right side</li>
 * <li>max. overlapping angle if opponent tries interception (right/left)</li>
 * <li>max. overlapping distance if opponent tries interception (right/left)
 * </li>
 * <li>....</li>
 * </ul
 */
public class PassEvaluatorResults {

  /**
   * creates an array of instances.
   * 
   * @param length -
   *          array length
   * @return
   */
  public static PassEvaluatorResults[] getInstances(final int length) {

    final PassEvaluatorResults[] results = new PassEvaluatorResults[length];
    for (int i = 0; i < results.length; i++) {
      results[i] = new PassEvaluatorResults();
    }
    return results;
  }

  // horizont
  private final int          maxCycle                   = DAInamiteConstants.PROPHET_MAX_CYCLES_PASSING;

  public static double EMPTY_VALUE                = Double.MAX_VALUE;

  // if an opponent gets the pass by mopre than MAX_OVERLAP degrees
  // the calculation stops
  public static double MAX_OVERLAP                = -20.0;

  // opponent is on left side of pass line
  private final boolean[]    isLeft                     = new boolean[11];

  // the nearest opponent from the left side has this angle
  // to the passline (by cycle)
  private final double[]     angleLeft                  = new double[this.maxCycle];

  // the nearest opponent from the right side has this angle
  // to the passline (by cycle)
  private final double[]     angleRight                 = new double[this.maxCycle];

  // same as above but now with distances instead of angles
  private final double[]     distanceLeft               = new double[this.maxCycle];

  private final double[]     distanceRight              = new double[this.maxCycle];

  // minAngle by opponent left side
  private final double[]     opponentAngleLeft          = new double[11];

  // minAngle by opponent right side
  private final double[]     opponentAngleRight         = new double[11];

  // no passing possible
  private boolean      noPassing                  = true;

  // min(angleLeft) (=min(opponentAngleLeft))
  private double       minAngleLeft               = PassEvaluatorResults.EMPTY_VALUE;

  // min(angleRight) (=min(opponentAngleRight))
  private double       minAngleRight              = PassEvaluatorResults.EMPTY_VALUE;

  // min overlapping distance left
  private double       minDistanceLeft            = PassEvaluatorResults.EMPTY_VALUE;

  // min overlapping distance right
  private double       minDistanceRight           = PassEvaluatorResults.EMPTY_VALUE;

  // the opponent intercepts and is on the left side
  private final boolean[]    interceptingOpponentsLeft  = new boolean[11];

  // the opponent intercepts and is on the right side
  private final boolean[]    interceptingOpponentsRight = new boolean[11];

  /**
   * Constructor of an object, which encapsulates the the results of a given
   * simulated pass.
   */
  public PassEvaluatorResults() {

    this.reset();
  }

  /**
   * resets all values
   * 
   */
  public void reset() {

    Arrays.fill(this.isLeft, false);
    Arrays.fill(this.angleLeft, PassEvaluatorResults.EMPTY_VALUE);
    Arrays.fill(this.angleRight, PassEvaluatorResults.EMPTY_VALUE);
    Arrays.fill(this.distanceLeft, PassEvaluatorResults.EMPTY_VALUE);
    Arrays.fill(this.distanceRight, PassEvaluatorResults.EMPTY_VALUE);
    Arrays.fill(this.opponentAngleLeft, PassEvaluatorResults.EMPTY_VALUE);
    Arrays.fill(this.opponentAngleRight, PassEvaluatorResults.EMPTY_VALUE);
    Arrays.fill(this.interceptingOpponentsLeft, false);
    Arrays.fill(this.interceptingOpponentsRight, false);
    this.minAngleLeft = PassEvaluatorResults.EMPTY_VALUE;
    this.minAngleRight = PassEvaluatorResults.EMPTY_VALUE;
    this.minDistanceLeft = PassEvaluatorResults.EMPTY_VALUE;
    this.minDistanceRight = PassEvaluatorResults.EMPTY_VALUE;
    this.noPassing = false;
  }

  /**
   * test if a double represents the unknown value.
   */
  public boolean isUnknown(final double d) {

    return d == PassEvaluatorResults.EMPTY_VALUE;
  }

  /**
   * stores the information that an opponent is on the left side of the pass
   * line.
   * 
   * @param playerIndex
   * @param left
   */
  public void setIsLeft(final int playerIndex,
      final boolean left) {

    this.isLeft[playerIndex] = left;
  }

  /**
   * opponent is on left side of pass line.
   * 
   * @param playerIndex
   * @return
   */
  public boolean isLeft(final int playerIndex) {

    return this.isLeft[playerIndex];
  }

  /**
   * sets the angle overlapping information.
   * 
   * @param playerIndex -
   *          the player by its index
   * @param cycle -
   *          the cycle
   * @param angle -
   *          the angle
   * 
   * @see PassEvaluator
   */
  public void setAngle(final int playerIndex,
      final int cycle,
      final double angle) {

    if (this.isLeft(playerIndex)) {
      this.angleLeft[cycle] = Math.min(this.angleLeft[cycle], angle);
      this.opponentAngleLeft[playerIndex] = Math.min(
          this.opponentAngleLeft[playerIndex], angle);
      this.minAngleLeft = Math.min(this.minAngleLeft, angle);
    }
    else {
      this.angleRight[cycle] = Math.min(this.angleRight[cycle], angle);
      this.opponentAngleRight[playerIndex] = Math.min(
          this.opponentAngleRight[playerIndex], angle);
      this.minAngleRight = Math.min(this.minAngleRight, angle);
    }
  }

  /**
   * gets the angle by player.
   * 
   * @param playerIndex -
   *          the player's index
   * @return the angle of the player rel. to the pass trajectory
   */
  public double getAngleByPlayer(final int playerIndex) {

    if (this.isLeft(playerIndex)) {
      return this.angleLeft[playerIndex];
    }
    return this.angleRight[playerIndex];
  }

  /**
   * sets the distance by player.
   * 
   * @param playerIndex -
   *          the player by its index
   * @param distance -
   *          the min. distance a player gets near the trajectory. If
   *          <code>distance</code> the player is able to intercept the pass.
   */
  public void setDistance(final int playerIndex,
      final double distance) {

    if (this.isLeft(playerIndex)) {
      this.distanceLeft[playerIndex] = Math.min(this.distanceLeft[playerIndex],
          distance);
      this.minDistanceLeft = Math.min(this.minDistanceLeft, distance);
    }
    else {
      this.distanceRight[playerIndex] = Math.min(
          this.distanceRight[playerIndex], distance);
      this.minDistanceRight = Math.min(this.minDistanceRight, distance);
    }
  }

  /**
   * @return Returns true if there is no secure passing through the opponents.
   */
  public boolean isNoPassing() {

    return this.noPassing || (this.minAngleLeft + this.minAngleRight < 0.0);
  }

  /**
   * @param noPassing
   *          The noPassing to set.
   */
  public void setNoPassing(final boolean noPassing) {

    this.noPassing = noPassing;
  }

  /**
   * tells if the evaluation is finsished.
   * 
   * @return true if finished
   */
  public boolean isFinished() {

    if (this.minAngleLeft == PassEvaluatorResults.EMPTY_VALUE || this.minAngleRight == PassEvaluatorResults.EMPTY_VALUE) {
      return false;
    }
    return this.minAngleLeft + this.minAngleRight < PassEvaluatorResults.MAX_OVERLAP;
  }

  /**
   * @return Returns the minimum relative angle opponents can come near the pass
   *         trajectory. If <0, the opponent might be able to intercept the
   *         pass.
   * 
   * Only opponents on the left side of the trajectory are considered.
   */
  public double getMinAngleLeft() {

    return this.minAngleLeft;
  }

  /**
   * @return Returns the minimum relative angle opponents can come near the pass
   *         trajectory. If <0, the opponent might be able to intercept the
   *         pass.
   * 
   * Only opponents on the right side of the trajectory are considered.
   */
  public double getMinAngleRight() {

    return this.minAngleRight;
  }

  /**
   * @return Returns the minimum distance opponents can come near the pass
   *         trajectory. If <0, the opponent might be able to intercept the
   *         pass.
   * 
   * Only opponents on the left side of the trajectory are considered.
   */
  public double getMinDistanceLeft() {

    return this.minDistanceLeft;
  }

  /**
   * @return Returns the minimum distance opponents can come near the pass
   *         trajectory. If <0, the opponent might be able to intercept the
   *         pass.
   * 
   * Only opponents on the right side of the trajectory are considered.
   */
  public double getMinDistanceRight() {

    return this.minDistanceRight;
  }

  /**
   * @return Returns the angles opponents can come near the pass trajectory. If
   *         <0, the opponent might be able to intercept the pass.
   * 
   * Only opponents on the left side of the trajectory are considered.
   */
  public double[] getAngleLeft() {

    return this.angleLeft;
  }

  /**
   * @return Returns the angles opponents can come near the pass trajectory. If
   *         <0, the opponent might be able to intercept the pass.
   * 
   * Only opponents on the right side of the trajectory are considered.
   */
  public double[] getAngleRight() {

    return this.angleRight;
  }

  /**
   * @return Returns the distances opponents can come near the pass trajectory.
   *         If <0, the opponent might be able to intercept the pass.
   * 
   * Only opponents on the left side of the trajectory are considered.
   * 
   */
  public double[] getDistanceLeft() {

    return this.distanceLeft;
  }

  /**
   * @return Returns the distances opponents can come near the pass trajectory.
   *         If <0, the opponent might be able to intercept the pass.
   * 
   * Only opponents on the right side of the trajectory are considered.
   */
  public double[] getDistanceRight() {

    return this.distanceRight;
  }

  /**
   * @return Returns whether an opponent is on the left side of the trajectory
   *         for all opponents.
   */
  public boolean[] getIsLeft() {

    return this.isLeft;
  }

  /**
   * @return Returns the angle to an opponent on the left side of the
   *         trajectory.
   */
  public double[] getOpponentAngleLeft() {

    return this.opponentAngleLeft;
  }

  /**
   * @return Returns the angle to an opponent on the right side of the
   *         trajectory.
   */
  public double[] getOpponentAngleRight() {

    return this.opponentAngleRight;
  }

  /**
   * the angle that can't be reached by opponents. (relative to the pass
   * trajectory)
   * 
   * @return angle (<0 if opponents get all passes)
   */
  public double getFreeAngle() {

    if (this.minAngleLeft == PassEvaluatorResults.EMPTY_VALUE || this.minAngleRight == PassEvaluatorResults.EMPTY_VALUE) {
      return PassEvaluatorResults.EMPTY_VALUE;
    }
    return this.minAngleLeft + this.minAngleRight;
  }

  /**
   * the opponents on the left side of the pass line that are able to get near
   * the pass
   */
  public boolean[] getOpponentIsInterceptingLeft() {

    for (int i = 0; i < this.interceptingOpponentsLeft.length; i++) {
      this.interceptingOpponentsLeft[i] = this.opponentAngleLeft[i] != PassEvaluatorResults.EMPTY_VALUE && this
          .isLeft(i);
    }
    return this.interceptingOpponentsLeft;
  }

  /**
   * the opponents on the right side of the pass line that are able to get near
   * the pass
   */
  public boolean[] getOpponentIsInterceptingRight() {

    for (int i = 0; i < this.interceptingOpponentsRight.length; i++) {
      this.interceptingOpponentsRight[i] = this.opponentAngleRight[i] != PassEvaluatorResults.EMPTY_VALUE && !this
          .isLeft(i);
    }
    return this.interceptingOpponentsRight;
  }

  /**
   * the max deviation of the pass line to the left side. (in most cases that
   * sould be the angle to the next opponent relative to the pass angle)
   * 
   * @param deviation -
   *          the max angle to consider opponets on the left side of the pass
   *          line
   */
  public void setInitAngleLeft(final double deviation) {

    this.minAngleLeft = deviation;
  }

  /**
   * the max deviation of the pass line to the right side. (in most cases that
   * sould be the angle to the next opponent relative to the pass angle)
   * 
   * @param deviation -
   *          the max angle to consider opponets on the right side of the pass
   *          line
   */
  public void setInitRightAngle(final double deviation) {

    this.minAngleRight = deviation;
  }

  /**
   * the middle between the intercepting opponents by angle. (the angle the pass
   * should be corrected by for being farest from interception)
   * 
   * @return
   */
  public double getAvgAngle() {

    return (-this.minAngleLeft + this.minAngleRight) / 2.0;
  }

  /**
   * Fortunatly there was a plugin for that.... :-)
   * 
   * toString methode: creates a String representation of the object
   * 
   * @return the String representation
   * @author info.vancauwenberge.tostring plugin
   */
  @Override
  public String toString() {

    final StringBuffer buffer = new StringBuffer();
    buffer.append("PassEvaluatorResults[");
    buffer.append("maxCycle = ").append(this.maxCycle);
    buffer.append(", EMPTY_VALUE = ").append(PassEvaluatorResults.EMPTY_VALUE);
    buffer.append(", MAX_OVERLAP = ").append(PassEvaluatorResults.MAX_OVERLAP);
    if (this.isLeft == null) {
      buffer.append(", isLeft = ").append("null");
    }
    else {
      buffer.append(", isLeft = ").append("[");
      for (int i = 0; i < this.isLeft.length; i++) {
        if (i != 0) {
          buffer.append(", ");
        }
        buffer.append(this.isLeft[i]);
      }
      buffer.append("]");
    }
    if (this.angleLeft == null) {
      buffer.append(", angleLeft = ").append("null");
    }
    else {
      buffer.append(", angleLeft = ").append("[");
      for (int i = 0; i < this.angleLeft.length; i++) {
        if (i != 0) {
          buffer.append(", ");
        }
        buffer.append(this.angleLeft[i]);
      }
      buffer.append("]");
    }
    if (this.angleRight == null) {
      buffer.append(", angleRight = ").append("null");
    }
    else {
      buffer.append(", angleRight = ").append("[");
      for (int i = 0; i < this.angleRight.length; i++) {
        if (i != 0) {
          buffer.append(", ");
        }
        buffer.append(this.angleRight[i]);
      }
      buffer.append("]");
    }
    if (this.distanceLeft == null) {
      buffer.append(", distanceLeft = ").append("null");
    }
    else {
      buffer.append(", distanceLeft = ").append("[");
      for (int i = 0; i < this.distanceLeft.length; i++) {
        if (i != 0) {
          buffer.append(", ");
        }
        buffer.append(this.distanceLeft[i]);
      }
      buffer.append("]");
    }
    if (this.distanceRight == null) {
      buffer.append(", distanceRight = ").append("null");
    }
    else {
      buffer.append(", distanceRight = ").append("[");
      for (int i = 0; i < this.distanceRight.length; i++) {
        if (i != 0) {
          buffer.append(", ");
        }
        buffer.append(this.distanceRight[i]);
      }
      buffer.append("]");
    }
    if (this.opponentAngleLeft == null) {
      buffer.append(", opponentAngleLeft = ").append("null");
    }
    else {
      buffer.append(", opponentAngleLeft = ").append("[");
      for (int i = 0; i < this.opponentAngleLeft.length; i++) {
        if (i != 0) {
          buffer.append(", ");
        }
        buffer.append(this.opponentAngleLeft[i]);
      }
      buffer.append("]");
    }
    if (this.opponentAngleRight == null) {
      buffer.append(", opponentAngleRight = ").append("null");
    }
    else {
      buffer.append(", opponentAngleRight = ").append("[");
      for (int i = 0; i < this.opponentAngleRight.length; i++) {
        if (i != 0) {
          buffer.append(", ");
        }
        buffer.append(this.opponentAngleRight[i]);
      }
      buffer.append("]");
    }
    buffer.append(", noPassing = ").append(this.noPassing);
    buffer.append(", minAngleLeft = ").append(this.minAngleLeft);
    buffer.append(", minAngleRight = ").append(this.minAngleRight);
    buffer.append(", minDistanceLeft = ").append(this.minDistanceLeft);
    buffer.append(", minDistanceRight = ").append(this.minDistanceRight);
    if (this.interceptingOpponentsLeft == null) {
      buffer.append(", interceptingOpponentsLeft = ").append("null");
    }
    else {
      buffer.append(", interceptingOpponentsLeft = ").append("[");
      for (int i = 0; i < this.interceptingOpponentsLeft.length; i++) {
        if (i != 0) {
          buffer.append(", ");
        }
        buffer.append(this.interceptingOpponentsLeft[i]);
      }
      buffer.append("]");
    }
    if (this.interceptingOpponentsRight == null) {
      buffer.append(", interceptingOpponentsRight = ").append("null");
    }
    else {
      buffer.append(", interceptingOpponentsRight = ").append("[");
      for (int i = 0; i < this.interceptingOpponentsRight.length; i++) {
        if (i != 0) {
          buffer.append(", ");
        }
        buffer.append(this.interceptingOpponentsRight[i]);
      }
      buffer.append("]");
    }
    buffer.append("]");
    return buffer.toString();
  }

}
